<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .blue {
            background: blue;
        }
        
        .red {
            background: red;
        }
    </style>
</head>

<body>

    <p>一秒钟后更新</p>
    <p><b>原来位置:</b>1,2,3,4</p>
    <p><b>变化后位置:</b>2,4,1,3</p>
    <p>背景变红的是更新过的</p>
    <div id="app"></div>
    <script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
    <script>
        const {
            ref,
            effect
        } = VueReactivity;

        const Text = Symbol(); //文本类型
        const Comment = Symbol(); //注释文本类型
        const Fragment = Symbol(); //Fragment类型
        function createRenderer(options) {
            const {
                createElement,
                setElementText,
                insert,
                pathProps,
                createText,
                setText,
                createComment

            } = options;

            function mountElement(vnode, container) {
                //给vnode。el挂载真实的DOM元素
                const el = vnode.el = createElement(vnode.type)

                if (typeof vnode.children == "string") {
                    setElementText(el, vnode.children);
                } else if (Array.isArray(vnode.children)) {
                    vnode.children.forEach(child => {
                        patch(null, child, el);
                    });
                }

                if (vnode.props) {
                    for (const key in vnode.props) {
                        pathProps(el, key, null, vnode.props[key]);

                    }
                }
                insert(el, container);
            }

            function patchChildren(n1, n2, container) {
                //新节点是不是文本节点
                if (typeof n2.children === "string") {
                    if (Array.isArray(n1.children)) {
                        n1.children.forEach((c) => unmounted(c))
                    }
                    //如果新节点是文本，就设置文字
                    setElementText(container, n2.children);
                } else if (Array.isArray(n2.children)) {
                    //完全移除简单算法的代码
                    //以下是双端算法的代码
                    patchKeyedChildren(n1, n2, container);

                } else {
                    //能跑到这里说明新节点不存在
                    //但是旧节点还是存在三种情况
                    if (Array.isArray(n1.chidren)) {
                        n1.chidren.forEach(c => unmounted(c))
                    } else if (typeof n1.chidren === "string") {
                        setElementText(container, "")
                    }
                    //如果没有旧节点，什么都不用做
                }
            }

            function patchKeyedChildren(n1, n2, container) {
                const oldChildren = n1.children;
                const newChildren = n2.children;

                //四个索引值
                let oldStartIdx = 0;
                let oldEndIdx = oldChildren.length - 1;

                let newStartIdx = 0;
                let newEndIdx = newChildren.length - 1;

                //四个索引指向的真实vNode节点
                let oldStartVNode = oldChildren[oldStartIdx];
                let oldEndVNode = oldChildren[oldEndIdx];

                let newStartVNode = newChildren[newStartIdx];
                let newEndVNode = newChildren[newEndIdx];
                //暂时不用while，通过修改demo的vnode来重现每一个步骤
                while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
                    //这里的步骤，对应书本上面的对比步骤
                    if (!oldStartVNode) {
                        oldStartVNode = oldChildren[++oldStartIdx]
                    } else if (!oldEndVNode) {
                        oldEndVNode = newChildren[--oldEndIdx]; //为什么这里是newChilden?搞不懂
                    } else if (oldStartVNode.key === newStartVNode.key) {
                        console.warn("命中步骤1===========")
                            //先更新内容
                        patch(oldStartVNode, newStartVNode, container);
                        //因为位置（都是尾部）一样，所以不需要位移

                        //完成后，需要更新索引值，之前的以下两行代码是错的，这里更正了
                        oldStartVNode = oldChildren[++oldStartIdx];
                        newStartVNode = newChildren[++newStartIdx];
                        //步骤1
                    } else if (oldEndVNode.key === newEndVNode.key) {
                        console.warn("命中步骤2===========")
                            //步骤2
                            //先更新内容
                        patch(oldEndVNode, newEndVNode, container);
                        //因为位置（都是尾部）一样，所以不需要位移

                        //完成后，需要更新索引值
                        //之前的以下两行代码是错的，这里更正了
                        oldEndVNode = oldChildren[--oldEndIdx];
                        newEndVNode = newChildren[--newEndIdx];


                        //为了重现单一步骤，先注释其他步骤的代码
                    } else if (oldStartVNode.key === newEndVNode.key) {
                        console.warn("命中步骤3===========")
                            //步骤3
                            //先更新内容
                        patch(oldStartVNode, newEndVNode, container);
                        //再位移
                        insert(oldStartVNode.el, container, oldEndVNode.el.nextSibling);
                        //完成后，需要更新索引值
                        //之前的以下两行代码是错的，这里更正了
                        oldStartVNode = oldChildren[++oldStartIdx];
                        newEndVNode = newChildren[--newEndIdx];

                    } else if (oldEndVNode.key === newStartVNode.key) {
                        console.warn("命中步骤4===========")
                            //步骤4
                            //先更新内容
                        patch(oldEndVNode, newStartVNode, container);
                        //再位移
                        insert(oldEndVNode.el, container, oldStartVNode.el);
                        //完成后，需要更新索引值
                        //之前的以下两行代码是错的，这里更正了
                        oldEndVNode = oldChildren[--oldEndIdx];
                        newStartVNode = newChildren[++newStartIdx];

                    } else {
                        //兜底非理想状态
                        //遍历旧的节点，视图找到与newStartVnode拥有一样key的节点
                        const idxInOld = oldChildren.findIndex(node => node.key === newStartVNode.key);
                        console.log("idxInOld", idxInOld, 'oldChildren', oldChildren, "newChildren", newChildren)
                        if (idxInOld > -1) {
                            const vnodeToMove = oldChildren[idxInOld];
                            //先打补丁
                            patch(vnodeToMove, newStartVNode, container);
                            //简vnodeTovmove。el移到oldStartnode.el之前
                            console.log("vnodeToMove.el", vnodeToMove, "oldStartVNode.el", oldStartVNode)
                            insert(vnodeToMove.el, container, oldStartVNode.el);
                            //indexInOld对应的真实节点已经移除到别处，因此设置undefined!!
                            oldChildren[idxInOld] = undefined;

                            newStartVNode = newChildren[++newStartIdx];
                        }

                    }


                }


            }
            /**
             * n1 旧节点
             * n2 新节点 
             **/
            function patchElement(n1, n2) {
                const el = n2.el = n1.el; //为patchChildren服务,只是这个在这里处理合适吗？？？！！！不应该再patchChildren那里么
                const oldProps = n1.props;
                const newProps = n2.props;
                //更新props
                for (const key in newProps) {
                    if (newProps[key] != oldProps[key]) {
                        pathProps(el, key, oldProps[key], newProps[key])
                    }
                }
                //移除新props不包括的就props
                for (const key in oldProps) {
                    if (!key in newProps) {
                        patchProps(el, key, oldProps[key], null)
                    }
                }
                //更新children
                patchChildren(n1, n2, el);
            }

            function patch(n1, n2, container) {
                //类型不一样，就卸载旧的先
                if (n1 && n1.type != n2.type) {
                    unmounted(n1);
                    n1 = null;

                }
                //n1不存在，意味着是挂载，则调用mountElement函数
                const {
                    type
                } = n2;
                console.log("n2 type", type)
                if (typeof type === "string") {
                    if (!n1) {
                        mountElement(n2, container);
                    } else {
                        //n1存在,意味着更新
                        patchElement(n1, n2);

                    }
                } else if (type === Text) {
                    //没有旧节点
                    if (!n1) {
                        const el = n2.el = createText(n2.children);
                        insert(el, container)
                    } else {
                        const el = n2.el = n1.el;
                        if (n2.children !== n1.children) {
                            setText(el, n2.children);
                        }
                    }

                } else if (type === Comment) {

                    //没有旧节点
                    if (!n1) {
                        const el = n2.el = createComment(n2.children);
                        insert(el, container)
                    } else {
                        const el = n2.el = n1.el;
                        if (n2.children !== n1.children) {
                            setText(el, n2.children);
                        }
                    }
                } else if (type === Fragment) {
                    if (!n1) {
                        //不存在旧节点直接挂载
                        n2.children.forEach(c => patch(null, c, container));
                    } else {
                        //如果就解决点处在，只需要更新Fragment的children即可，
                        //这个需要在卸载函数里面特殊处理
                        patchChildren(n1, n2, container)
                    }
                } else if (typeof type === "object") {

                } else if (typeof type === "xxx") {

                }

            }
            //卸载函数
            function unmounted(vnode) {
                if (vnode.type === Fragment) {
                    vnode.children.forEach(c => unmounted(c))
                    return;
                }
                //根据vnode获取要卸载的真实dom元素
                const el = vnode.el;
                const parent = el.parentNode; //通过真实的dom父级来卸载，而不是vnode
                if (parent) {
                    parent.removeChild(el);
                };
            }
            //客户端渲染
            function render(vnode, container) {
                if (vnode) {
                    //新的vnode存在，将其于旧的vnode一起传给patch函数，进行打补丁
                    patch(container._vnode, vnode, container);
                } else {
                    if (container._vnode) {
                        //container.innerHTML="";
                        unmounted(container._vnode);
                    }
                }

                //将vnode存储到container._vnode下，后续渲染中的旧vnode
                container._vnode = vnode;
            }
            //服务器渲染
            function hydrate(vnode, container) {

            }
            return {
                render,
                hydrate
            }
        }

        function shouldSetAsProps(el, key, value) {
            //特殊处理
            if (key === 'form' && el.tagName === 'INPUT') {
                return false;
            }
            return key in el;
        }
        //使用============
        const renderer = createRenderer({
            createElement(tag) {
                return document.createElement(tag);
            },
            setElementText(el, text) {
                el.textContent = text;
            },
            insert(el, parent, anchor = null) {
                parent.insertBefore(el, anchor);
            },
            createText(text) {
                return document.createTextNode(text);
            },
            setText(el, text) {
                el.nodeValue = text;
            },
            createComment(text) {
                return document.createComment(text);
            },
            pathProps(el, key, prevValue, nextValue) {
                if (/^on/.test(key)) {
                    const invokers = el._vei || (el._vei = {});
                    let invoker = invokers[key];
                    const name = key.slice(2).toLowerCase();
                    console.log("点击事件", name, nextValue)
                    if (nextValue) {
                        if (!invoker) {
                            invoker = el._vei[key] = (e) => {
                                //触发事件小于绑定事件，不执行任何回调
                                //e.timeStamp 不同浏览器，可能是精度时间，也可能不是精度时间
                                if (e.timeStamp <= invoker.attacked) {
                                    return;
                                }
                                if (Array.isArray(invoker.value)) {
                                    invoker.value.forEach(fn => {
                                        fn(e);
                                    });
                                } else {
                                    invoker.value(e);
                                }

                            }
                            invoker.value = nextValue;
                            invoker.attacked = performance.now();
                            el.addEventListener(name, invoker);
                        } else {
                            invoker.value = nextValue;
                        }

                    } else if (invoker) {
                        el.removeEventListener(name, invoker);
                    }
                } else if (key === 'class') {
                    el.className = nextValue || "";
                } else if (shouldSetAsProps(el, key, nextValue)) {
                    //获取属性对应的值的类型
                    const type = typeof el[key];
                    console.log("key", key, typeof type)
                    if (typeof type === 'boolean' && nextValue === "") {
                        el[key] = true;
                    } else {
                        el[key] = nextValue;
                    }
                } else {
                    //el[key] = nextValue; //dom props优先
                    el.setAttribute(key, nextValue);
                }
            }
        });

        function normalizeClass(arr) {
            let t = "";
            for (let k = 0; k < arr.length; k++) {
                let item = arr[k];
                if (typeof item === 'string') {
                    t += " " + item;
                } else if (typeof item === "object") {
                    for (let key in item) {
                        if (item[key]) {
                            t += " " + key;
                        }
                    }
                }
            }
            return t;
        }

        const bol = ref(true);
        console.log("Text", Text)
            //一定要研究虚拟dom位置的差异
        effect(() => {
                const vnode = bol.value ? {
                    type: "div",
                    key: 1,
                    children: [{
                        type: "p",
                        key: 1,
                        children: "p-1",
                        props: {
                            class: "blue"
                        }
                    }, {
                        type: "p",
                        key: 2,
                        children: "p-2",
                        props: {
                            class: "blue"
                        }

                    }, {
                        type: "p",
                        key: 3,
                        children: "p-3",
                        props: {
                            class: "blue"
                        }
                    }, {
                        type: "p",
                        key: 4,
                        children: "p-4",
                        props: {
                            class: "blue"
                        }
                    }]
                } : {
                    type: "div",
                    key: 1,
                    children: [{
                        type: "p",
                        key: 2,
                        children: "p-2",
                        props: {
                            class: "red"
                        }

                    }, {
                        type: "p",
                        key: 4,
                        children: "p-4",
                        props: {
                            class: "red"
                        }
                    }, {
                        type: "p",
                        key: 1,
                        children: "p-1",
                        props: {
                            class: "red"
                        }


                    }, {
                        type: "p",
                        key: 3,
                        children: "p-3",
                        props: {
                            class: "red"
                        }

                    }]
                }
                const app = document.querySelector("#app");

                //首次渲染
                renderer.render(vnode, app);
            })
            //用来更新vnode渲染
        setTimeout(() => {
            console.log("触发更新!!!!!!!")
            bol.value = false;
        }, 1500)
    </script>
</body>

</html>